/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.filesystems; import java.beans.*; import java.io.IOException; import java.text.MessageFormat; import java.util.LinkedList; import java.util.Iterator; import javax.swing.SwingUtilities; import javax.swing.event.EventListenerList; import org.openide.execution.NbfsStreamHandlerFactory; import org.openide.util.actions.SystemAction; import org.openide.util.Queue; import org.openide.util.NbBundle; import org.openide.util.enum.QueueEnumeration; /** Interface that provides basic information about a virtual * filesystem in the IDE. Classes that implement it * should follow JavaBean conventions because when a new * instance of a file system class is inserted into the system, it should * permit the user to modify it with standard Bean properties. * <P> * Implementing classes should also have associated subclasses of {@link FileObject}. * <p>Although the class is serializable, only the {@link #isHidden hidden state} and {@link #getSystemName system name} * are serialized, and the deserialized object is by default {@link #isValid invalid} (and may be a distinct * object from a valid file system in the Repository). If you wish to safely deserialize a file * system, you should after deserialization try to replace it with a file system of the * {@link Repository#findFileSystem same name} in the Repository. * @author Jaroslav Tulach */ public abstract class FileSystem implements java.io.Serializable { /** generated Serialized Version UID */ static final long serialVersionUID = -8931487924240189180L; /** Property name indicating validity of file system. */ public static final String PROP_VALID = "valid"; // NOI18N /** Property name indicating whether file system is hidden. */ public static final String PROP_HIDDEN = "hidden"; // NOI18N /** Property name giving internal system name of file system. */ public static final String PROP_SYSTEM_NAME = "systemName"; // NOI18N /** Property name giving root folder of file system. */ public static final String PROP_ROOT = "root"; // NOI18N /** Property name giving read-only state. */ public static final String PROP_READ_ONLY = "readOnly"; // NOI18N /** is this file system valid? * It can be invalid if there is another file system with the * same name in the file system pool. */ transient private boolean valid = false; /** True if the file system is assigned to pool. * Is modified from Repository methods. */ transient boolean assigned = false; /** Describes capabilities of the file system. */ private FileSystemCapability capability = new FileSystemCapability.Bean (); /** number of requests posted and not processed. to * know what to do in sync. */ private static int requests; /** List of requests */ private static QueueEnumeration requestsQueue; /** hidden flag */ private boolean hidden = false; /** system name */ private String systemName = "".intern (); // NOI18N /** Utility field used by event firing mechanism. */ private transient javax.swing.event.EventListenerList listenerList; /** Test whether file system is valid. * Generally invalidity would be caused by a name conflict in the file system pool. * @return true if the file system is valid */ public final boolean isValid () { return valid; } /** Setter for validity. Accessible only from file system pool. * @param v the new value */ final void setValid (boolean v) { if (v != valid) { valid = v; firePropertyChange (PROP_VALID, new Boolean (!v), new Boolean (v)); } } /** Set hidden state of the object. * A hidden file system is not presented to the user in the Repository list (though it may be present in the Repository Settings list). * * @param hide <code>true</code> if the file system should be hidden */ public final void setHidden (boolean hide) { if (hide != hidden) { hidden = hide; firePropertyChange (PROP_HIDDEN, new Boolean (!hide), new Boolean (hide)); } } /** Getter for the hidden property. */ public final boolean isHidden () { return hidden; } /** Tests whether file system will survive reloading of system pool. * If true then when * {@link Repository} is reloading its content, it preserves this * file system in the pool. * <P> * This can be used when the pool contains system level and user level * file systems. The system ones should be preserved when the user changes * the content (for example when he is loading a new project). * <p>The default implementation returns <code>false</code>. * * @return true if the file system should be persistent */ protected boolean isPersistent () { return false; } /** Provides a name for the system that can be presented to the user. * @return user presentable name of the file system */ public abstract String getDisplayName (); /** Internal (system) name of the file system. * Should uniquely identify the file system, as it will * be used during serialization of its files. The preferred way of doing this is to concatenate the * name of the file system type (e.g. the class) and the textual form of its parameters. * <P> * A change of the system name should be interpreted as a change of the internal * state of the file system. For example, if the root directory is moved to different * location, one should rebuild representations for all files * in the system. * * @return string with system name */ public final String getSystemName () { return systemName; } /** Changes system name of the file system. * This property is bound and constrained: first of all * all vetoable listeners are asked whether they agree with the change. If so, * the change is made and all change listeners are notified of * the change. * * <p><em>Warning:</em> this method is protected so that only subclasses can change * the system name. * * @param name new system name * @exception PropertyVetoException if the change is not allowed by a listener */ protected final void setSystemName (String name) throws PropertyVetoException { synchronized (Repository.class) { // I must be the only one who works with system pool (that is listening) // on this interface fireVetoableChange (PROP_SYSTEM_NAME, systemName, name); String old = systemName; systemName = name.intern (); firePropertyChange (PROP_SYSTEM_NAME, old, systemName); } } /** Returns <code>true</code> if the filesystem is default one of the IDE. * @see Repository#getDefaultFileSystem */ public final boolean isDefault () { return this == org.openide.TopManager.getDefault ().getRepository ().getDefaultFileSystem (); } /** Test if the filesystem is read-only or not. * @return true if the system is read-only */ public abstract boolean isReadOnly (); /** Getter for root folder in the filesystem. * * @return root folder of whole filesystem */ public abstract FileObject getRoot (); /** Finds file in the filesystem by name. * <P> * The default implementation converts dots in the package name into slashes, * concatenates the strings, adds any extension prefixed by a dot and calls * the {@link #findResource findResource} method. * * <p><em>Note:</em> when both of <code>name</code> and <code>ext</code> are <CODE>null</CODE> then name and * extension should be ignored and scan should look only for a package. * * @param aPackage package name where each package component is separated by a dot * @param name name of the file (without dots) or <CODE>null</CODE> if * one wants to obtain a folder (package) and not a file in it * @param ext extension of the file (without leading dot) or <CODE>null</CODE> if one needs * a package and not a file * * @return a file object that represents a file with the given name or * <CODE>null</CODE> if the file does not exist */ public FileObject find (String aPackage, String name, String ext) { StringBuffer bf = new StringBuffer (); // append package and name if (!aPackage.equals ("")) { // NOI18N String p = aPackage.replace ('.', '/'); bf.append (p); bf.append ('/'); } // append name if (name != null) { bf.append (name); } // append extension if there is one if (ext != null) { bf.append ('.'); bf.append (ext); } return findResource (bf.toString ()); } /** Finds file when its resource name is given. * The name has the usual format for the {@link ClassLoader#getResource(String)} * method. So it may consist of "package1/package2/filename.ext". * If there is no package, it may consist only of "filename.ext". * * @param name resource name * * @return FileObject that represents file with given name or * <CODE>null</CODE> if the file does not exist */ public abstract FileObject findResource (String name); /** Returns an array of actions that can be invoked on any file in * this file system. * These actions should preferably * support the {@link org.openide.util.actions.Presenter.Menu Menu}, * {@link org.openide.util.actions.Presenter.Popup Popup}, * and {@link org.openide.util.actions.Presenter.Toolbar Toolbar} presenters. * * @return array of available actions */ public abstract SystemAction[] getActions (); /** Reads object from stream and creates listeners. * @param in the input stream to read from * @exception IOException error during read * @exception ClassNotFoundException when class not found */ private void readObject (java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException { in.defaultReadObject (); if (capability == null) { capability = new FileSystemCapability.Bean (); } } public String toString () { return getSystemName () + "[" + getClass ().getName () + "]"; // NOI18N } /** Allows filesystems to set up the environment for external execution * and compilation. * Each filesystem can add its own values that * influence the environment. The set of operations that can modify * environment is described by the {@link Environment} interface. * <P> * The default implementation throws an exception to signal that it does not * support external compilation or execution. * * @param env the environment to setup * @exception EnvironmentNotSupportedException if external execution * and compilation cannot be supported */ public void prepareEnvironment (Environment env) throws EnvironmentNotSupportedException { throw new EnvironmentNotSupportedException (this); } /** Get a status object that can annotate a set of files by changing the names or icons * associated with them. * <P> * The default implementation returns a status object making no modifications. * * @return the status object for this file system */ public Status getStatus () { return STATUS_NONE; } /** The object describing capabilities of this filesystem. * Subclasses can override it. */ public final FileSystemCapability getCapability () { return capability; } /** Allows subclasses to change a set of capabilities of the * file system. * @param capability the capability to use */ protected final void setCapability (FileSystemCapability capability) { this.capability = capability; } /** Executes atomic action. The atomic action represents a set of * operations constituting one logical unit. It is guaranteed that during * execution of such an action no events about changes in the file system * will be fired. * <P> * <em>Warning:</em> the action should not take a significant amount of time, and should finish as soon as * possible--otherwise all event notifications will be blocked. * * @param run the action to run * @exception IOException if there is an <code>IOException</code> thrown in the actions' {@link AtomicAction#run run} * method */ public final void runAtomicAction (final AtomicAction run) throws IOException { try { enterAtomicAction (); run.run (); } finally { exitAtomicAction (); } } /** Enters atomic action. */ private static synchronized void enterAtomicAction () { if (requests++ == 0) { requestsQueue = new QueueEnumeration (); } } /** Exits atomic action. */ private static void exitAtomicAction () { java.util.Enumeration myQueue; synchronized (FileSystem.class) { if (--requests == 0) { myQueue = requestsQueue; requestsQueue = null; } else { myQueue = null; } } if (myQueue != null) { while (myQueue.hasMoreElements()) { Runnable r = (Runnable)myQueue.nextElement(); r.run(); } } } /** Adds an event dispatcher to the queue of FS events. * @param run dispatcher to run */ static void putEventDispatcher (EventDispatcher run) { synchronized (FileSystem.class) { if (requestsQueue != null) { // run later requestsQueue.put (run); return; } } // run now! run.run (); } /** Registers FileStatusListener to receive events. * The implementation registers the listener only when getStatus () is * overriden to return a special value. * * @param listener The listener to register. */ public final synchronized void addFileStatusListener ( org.openide.filesystems.FileStatusListener listener ) { // JST: Ok? Do not register listeners when the fs cannot change status? if (getStatus () == STATUS_NONE) return; if (listenerList == null) listenerList = new EventListenerList (); listenerList.add (org.openide.filesystems.FileStatusListener.class, listener); } /** Removes FileStatusListener from the list of listeners. *@param listener The listener to remove. */ public final synchronized void removeFileStatusListener ( org.openide.filesystems.FileStatusListener listener ) { if (listenerList == null) return; listenerList.remove (org.openide.filesystems.FileStatusListener.class, listener); } /** Notifies all registered listeners about change of status of some files. * * @param e The event to be fired */ protected final void fireFileStatusChanged(FileStatusEvent event) { if (listenerList == null) return; Object[] listeners = listenerList.getListenerList (); for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==org.openide.filesystems.FileStatusListener.class) { ((org.openide.filesystems.FileStatusListener)listeners[i+1]).annotationChanged (event); } } } /** Adds listener for the veto of property change. * @param listener the listener */ public final synchronized void addVetoableChangeListener( java.beans.VetoableChangeListener listener ) { if (listenerList == null) listenerList = new EventListenerList (); listenerList.add (java.beans.VetoableChangeListener.class, listener); } /** Removes listener for the veto of property change. * @param listener the listener */ public final synchronized void removeVetoableChangeListener( java.beans.VetoableChangeListener listener ) { if (listenerList == null) return; listenerList.remove (java.beans.VetoableChangeListener.class, listener); } /** Fires property vetoable event. * @param name name of the property * @param o old value of the property * @param n new value of the property * @exception PropertyVetoException if an listener vetoed the change */ protected final void fireVetoableChange ( java.lang.String name, java.lang.Object o, java.lang.Object n ) throws PropertyVetoException { if (listenerList == null) return; java.beans.PropertyChangeEvent e = null; Object[] listeners = listenerList.getListenerList (); for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==java.beans.VetoableChangeListener.class) { if (e == null) e = new java.beans.PropertyChangeEvent (this, name, o, n); ((java.beans.VetoableChangeListener)listeners[i+1]).vetoableChange (e); } } } /** Registers PropertyChangeListener to receive events. *@param listener The listener to register. */ public final synchronized void addPropertyChangeListener( java.beans.PropertyChangeListener listener ) { if (listenerList == null) listenerList = new EventListenerList (); listenerList.add (java.beans.PropertyChangeListener.class, listener); } /** Removes PropertyChangeListener from the list of listeners. *@param listener The listener to remove. */ public final synchronized void removePropertyChangeListener( java.beans.PropertyChangeListener listener ) { if (listenerList == null) return; listenerList.remove (java.beans.PropertyChangeListener.class, listener); } /** Fires property change event. * @param name name of the property * @param o old value of the property * @param n new value of the property */ protected final void firePropertyChange (String name, Object o, Object n) { if (listenerList == null) return; java.beans.PropertyChangeEvent e = null; Object[] listeners = listenerList.getListenerList (); for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==java.beans.PropertyChangeListener.class) { if (e == null) e = new java.beans.PropertyChangeEvent (this, name, o, n); ((java.beans.PropertyChangeListener)listeners[i+1]).propertyChange (e); } } } /** An action that it is to be called atomically with respect to file system event notification. * During its execution (via {@link FileSystem#runAtomicAction runAtomicAction}) * no events about changes in file systems are fired. */ public static interface AtomicAction { /** Executed when it is guaranteed that no events about changes * in filesystems will be notified. * * @exception IOException if there is an error during execution */ public void run () throws IOException; } /** Interface that allows filesystems to set up the Java environment * for external execution and compilation. * Currently just used to append entries to the external class path. */ public static abstract class Environment extends Object { /** Adds one element to the class path environment variable. * @param classPathElement string representing the one element */ public void addClassPath (String classPathElement) { } } /** Allows a filesystem to annotate a group of files (typically comprising a data object) with additional markers. * <p>This could be useful, for * example, for a filesystem supporting version control. * It could annotate names and icons of data nodes according to whether the files were current, locked, etc. */ public static interface Status { /** Annotate the name of a file cluster. * @param name the name suggested by default * @param files an immutable set of {@link FileObject}s belonging to this filesystem * @return the annotated name (may be the same as the passed-in name) * @exception ClassCastException if the files in the set are not of valid types */ public String annotateName (String name, java.util.Set files); /** Annotate the icon of a file cluster. * <p>Please do <em>not</em> modify the original; create a derivative icon image, * using a weak-reference cache if necessary. * @param icon the icon suggested by default * @param iconType an icon type from {@link java.beans.BeanInfo} * @param files an immutable set of {@link FileObject}s belonging to this filesystem * @return the annotated icon (may be the same as the passed-in icon) * @exception ClassCastException if the files in the set are not of valid types */ public java.awt.Image annotateIcon (java.awt.Image icon, int iconType, java.util.Set files); } /** Empty status */ private static final Status STATUS_NONE = new Status () { public String annotateName (String name, java.util.Set files) { return name; } public java.awt.Image annotateIcon (java.awt.Image icon, int iconType, java.util.Set files) { return icon; } }; /** Class used to notify events for the file system. */ static abstract class EventDispatcher extends Object implements Runnable { public final void run () { dispatch (); } protected abstract void dispatch (); } /** Getter for the resource string * @param s the resource name * @return the resource */ static String getString(String s) { return NbBundle.getBundle("org.openide.filesystems.Bundle").getString (s); } /** Creates message for given string property with one parameter. * @param s resource name * @param obj the parameter to the message * @return the string for that text */ static String getString (String s, Object obj) { return MessageFormat.format (getString (s), new Object[] { obj }); } /** Creates message for given string property with two parameters. * @param s resource name * @param obj1 the parameter to the message * @param obj2 the parameter to the message * @return the string for that text */ static String getString (String s, Object obj1, Object obj2) { return MessageFormat.format (getString (s), new Object[] { obj1, obj2 }); } /** Creates message for given string property with three parameters. * @param s resource name * @param obj1 the parameter to the message * @param obj2 the parameter to the message * @param obj3 the parameter to the message * @return the string for that text */ static String getString (String s, Object obj1, Object obj2, Object obj3) { return MessageFormat.format (getString (s), new Object[] { obj1, obj2, obj3 }); } } /* * Log * 29 Gandalf 1.28 1/12/00 Jesse Glick [JavaDoc] * 28 Gandalf 1.27 1/12/00 Ian Formanek NOI18N * 27 Gandalf 1.26 11/30/99 Ales Novak exitAtomicAction * 26 Gandalf 1.25 11/2/99 Jaroslav Tulach Deleted PROP_STATUS * 25 Gandalf 1.24 10/29/99 Jaroslav Tulach MultiFileSystem + * FileStatusEvent * 24 Gandalf 1.23 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 23 Gandalf 1.22 10/1/99 Jaroslav Tulach FileObject.move & * FileObject.copy * 22 Gandalf 1.21 9/1/99 Jaroslav Tulach The DataNode reacts to * changes in FileSystem.getStatus by updating its name and icon. * 21 Gandalf 1.20 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 20 Gandalf 1.19 6/7/99 Jaroslav Tulach More backward * compatible. * 19 Gandalf 1.18 6/7/99 Jaroslav Tulach FS capabilities. * 18 Gandalf 1.17 6/1/99 Jaroslav Tulach Changes made during run * of runAtomicAction are fired synchronously at the end of * runAtomicActions * 17 Gandalf 1.16 6/1/99 Jaroslav Tulach synchronization on * atomic actions of FS * 16 Gandalf 1.15 4/20/99 Jesse Glick [JavaDoc], and added * toString(). * 15 Gandalf 1.14 4/12/99 Jesse Glick [JavaDoc] * 14 Gandalf 1.13 3/26/99 Jesse Glick [JavaDoc] * 13 Gandalf 1.12 3/26/99 Jaroslav Tulach * 12 Gandalf 1.11 3/24/99 Jaroslav Tulach * 11 Gandalf 1.10 3/21/99 Jaroslav Tulach Repository displayed ok. * 10 Gandalf 1.9 3/19/99 Jaroslav Tulach TopManager.getDefault * ().getRegistry () * 9 Gandalf 1.8 3/15/99 Jesse Glick [JavaDoc] * 8 Gandalf 1.7 3/13/99 Jaroslav Tulach FileSystem.Status & * lastModified * 7 Gandalf 1.6 3/1/99 Jesse Glick [JavaDoc] * 6 Gandalf 1.5 2/11/99 Ian Formanek Renamed FileSystemPool * -> Repository * 5 Gandalf 1.4 2/5/99 Jesse Glick [JavaDoc] * 4 Gandalf 1.3 2/4/99 Jesse Glick [JavaDoc] * 3 Gandalf 1.2 2/1/99 Jesse Glick [JavaDoc] * 2 Gandalf 1.1 1/11/99 Jaroslav Tulach NbClassLoader extends * URLClassLoader * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ * Beta Change History: * 0 Tuborg 0.25 --/--/98 Jan Formanek added and removed getIcon * 0 Tuborg 0.26 --/--/98 Jaroslav Tulach added setter and getter for hidden property, listeners support * 0 Tuborg 0.26 --/--/98 Jaroslav Tulach changed * 0 Tuborg 0.27 --/--/98 Jaroslav Tulach thread for firing events in filesystem + runAtomicAction method * 0 Tuborg 0.28 --/--/98 Jaroslav Tulach environment support * 0 Tuborg 0.29 --/--/98 Petr Hamernik URL protocol * 0 Tuborg 0.30 --/--/98 Jaroslav Tulach added method for finding of resources (for use with URL) * 0 Tuborg 0.31 --/--/98 Ales Novak NbfsURLConstants * 0 Tuborg 0.32 --/--/98 Jaroslav Tulach cleared filedispatch thread, invokeLater is used instead */